﻿using LeRenard.Libraries.PlatformInspection.InfoNodes;
using System;
using System.Collections.Generic;
using System.Linq;

namespace LeRenard.Libraries.PlatformInspection.Inspectors
{
    /// <summary>
    /// Base implementation of an <see cref="IInspector"/>.
    /// </summary>
    /// <typeparam name="TInspector">The type of the <see cref="IInspector"/> this module belongs to.</typeparam>
    public abstract class Inspector<TInspector> : IInspector
    {
        #region Members
        /// <summary>
        /// Gets the registered modules for this inspector.
        /// </summary>
        private IEnumerable<IInspectorModule<TInspector>> Modules { get { return _modules; } }
        private List<IInspectorModule<TInspector>> _modules;
        #endregion

        #region Construction
        /// <summary>
        /// Initializes a new <see cref="Inspector"/>.
        /// </summary>
        /// <param name="name">The name of the <see cref="Inspector"/>.</param>
        /// <param name="description">The description of the <see cref="Inspector"/>.</param>
        public Inspector(string name, string description)
        {
            ThrowIf.NullOrEmpty(() => name);
            ThrowIf.NullOrEmpty(() => description);

            _name = name;
            _description = description;
            _modules = new List<IInspectorModule<TInspector>>();

            AutoRegisterModules();
        }
        #endregion

        #region IInspector
        /// <summary>
        /// Gets the <see cref="IInspector"/> name.
        /// </summary>
        public string Name { get { return _name; } }
        private readonly string _name;

        /// <summary>
        /// Gets the <see cref="IInspector"/> description.
        /// </summary>
        public string Description { get { return _description; } }
        private readonly string _description;

        /// <summary>
        /// Gets the information this inspector gathers from it's modules.
        /// </summary>        
        public InfoNode GetInformation()
        {
            // Prepare root node
            var rootInfoNode = new InfoNode
            (
                name: Name,
                displayStrategy: InfoNodeEmptyValueDisplayStrategy.TreatAsTitleInfoNode
            );

            // Clean Exceptions
            _exceptions = new List<ExceptionInfo>();

            // Gather all info from the modules
            foreach (var module in Modules)
            {
                try
                {
                    var moduleInfoNode = module.GetInformation();
                    rootInfoNode.AddInfoNode(moduleInfoNode);
                    
                    var moduleName = module.GetType().ToFriendlyName();
                    var exceptionInfos = module.Exceptions
                        .Select(e => new ExceptionInfo(moduleName, e));
                    _exceptions.AddRange(exceptionInfos);
                }
                catch (Exception ex)
                {
                    var exceptionInfo = new ExceptionInfo(this.GetType().ToFriendlyName(), ex);
                    _exceptions.Add(exceptionInfo);
                }
            }

            // Return info
            return rootInfoNode;
        }

        /// <summary>
        /// Gets the exceptions occured during the last call of <see cref="GetInformation"/>.
        /// </summary>        
        public IEnumerable<ExceptionInfo> Exceptions { get { return _exceptions; } }
        private List<ExceptionInfo> _exceptions;
        #endregion

        #region Methods
        /// <summary>
        /// Registers an <see cref="IInspectorModule"/>.
        /// </summary>        
        public void RegisterModule(IInspectorModule<TInspector> module)
        {
            if (!_modules.Contains(module))
                _modules.Add(module);
        }

        /// <summary>
        /// Unregisters an <see cref="IInspectorModule"/>.
        /// </summary>        
        public void UnregisterModule(IInspectorModule<TInspector> module)
        {
            if (_modules.Contains(module))
                _modules.Remove(module);
        }

        /// <summary>
        /// Clears all modules.
        /// </summary>
        public void ClearModules()
        {
            _modules.Clear();
        }

        /// <summary>
        /// Automatically scans for modules and registers them.
        /// </summary>
        public void AutoRegisterModules()
        {
            var modules = new List<IInspectorModule<TInspector>>();

            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (var type in assembly.GetTypes())
                {
                    if (!type.IsAbstract
                        && !type.IsInterface
                        && type.IsClass
                        && type.GetInterfaces().Contains(typeof(IInspectorModule<TInspector>)))
                    {
                        var module = (IInspectorModule<TInspector>)Activator.CreateInstance(type);
                        modules.Add(module);
                    }
                }
            }

            foreach (var module in modules)
                RegisterModule(module);
        }
        #endregion
    }
}
